# 存储软件包间依赖关系
from utils import *
from CompareVersion import verSatisfyAll
import argparse

# 如果发现没有符合要求的包，就先按照名字创建一个，id也为pkg_name
def create_unfinish_pkg(tx, pkg_name):
    # 先检查之前是否创建过包，如果已经创建过unfinish包，则不创建
    query = (
        "MATCH (n:Package {Id: '%s', IsFinish: $is_finish}) RETURN n"
    ) % pkg_name
    results = tx.run(query, is_finish=False).values("n")
    if len(results) != 0:
        return
    query = (
        "CREATE (n:Package{Id: '%s', Name: '%s', Version: '', "
        "Architecture: '', IsFinish: $is_finish})"
        "RETURN id(n)" % (pkg_name, pkg_name)
    )
    tx.run(query, is_finish=False)


# 给定两个包的id，创建关系
def create_rela_by_id(tx, ori_pkg_id, depend_pkg_id, rela_type, isFinish, isReplaceable, unFinishVersionList, replaceStr):
    # 先检查关系是否存在
    query = (
        "MATCH (m:Package {Id:$ori_pkg_id})-[:%s]->(n:Package {Id:$depend_pkg_id}) "
        "RETURN m" %rela_type.replace('-', '_')
    )
    res = tx.run(query, ori_pkg_id=ori_pkg_id, depend_pkg_id=depend_pkg_id).values("m")
    if len(res) != 0:  # 存在就不创建
        return
    query = (
        "MATCH (m:Package) WHERE (m.Id = $ori_pkg_id) "
        "MATCH (n:Package) WHERE (n.Id = $depend_pkg_id)  "
        "MERGE (m)-[:%s{IsFinish: $isFinish, IsReplaceable: $isReplaceable,"
        "UnFinishVersion: $unFinishVersion, ReplaceStr: $replaceStr}]->(n) "
        "RETURN m,n" %rela_type.replace('-', '_')
    )
    tx.run(query, ori_pkg_id=ori_pkg_id, depend_pkg_id=depend_pkg_id,
           isFinish=isFinish, isReplaceable=isReplaceable,
           unFinishVersion=str(unFinishVersionList), replaceStr=replaceStr)


# 给定关系发出包的id和接收关系包的name，创建关系
def create_rela_by_name(tx, ori_pkg_id, depend_pkg_name, rela_type, isFinish, isReplaceable, unFinishVersionList, replaceStr):
    # 检查关系是否存在
    query = (
        "MATCH (m:Package {Id:$ori_pkg_id})-[:%s]->(n:Package {Name:$depend_pkg_name}) "
        "RETURN m" %rela_type.replace('-', '_')
    )
    res = tx.run(query, ori_pkg_id=ori_pkg_id, depend_pkg_name=depend_pkg_name).values("m")
    if len(res) != 0:  # 存在就不创建
        return

    query = (  # 这样就把所有的都合并了吗？调试一下回头
        "MATCH (m:Package) WHERE (m.Id = $ori_pkg_id) "
        "MATCH (n:Package) WHERE (n.Name = $depend_pkg_name)  "
        "MERGE (m)-[:%s{IsFinish: $isFinish, IsReplaceable: $isReplaceable,"
        "UnFinishVersion: $unFinishVersion, ReplaceStr: $replaceStr}]->(n) "
        "RETURN m,n" %rela_type.replace('-', '_')
    )
    tx.run(query, ori_pkg_id=ori_pkg_id, depend_pkg_name=depend_pkg_name,
           isFinish=isFinish, isReplaceable=isReplaceable,
           unFinishVersion=str(unFinishVersionList), replaceStr=replaceStr)


# 给定两个包，添加它们的关系，唯一刻画包的表征方式：Name+Version(+Arch)
# constr_str包括一个包及其版本要求，这个函数要找到满足要求的这个包
# 关系具有四个属性：是否完成，是否可替换，未完成版本（若），可替代字符串（若为唯一依赖，则为空串）
def add_rela(tx, rela_type, package_dict, depend_pkg_name, constr_str_list, isReplaceable, replaceStr=""):
    ori_pkg_id = package_dict['Package'] + get_value(package_dict, 'Version')

    # 先检查被依赖包是否存在，根据Name查
    query = (
        "MATCH (n:Package {Name: '%s'}) RETURN n"
    ) % depend_pkg_name
    results = tx.run(query).values("n")
    if len(results) == 0:  # 没有匹配到包
        create_unfinish_pkg(tx, depend_pkg_name)
        depend_pkg_id = depend_pkg_name
        # 添加指向关系，在关系属性中注明此依赖仍未完成
        create_rela_by_id(tx, ori_pkg_id, depend_pkg_id, rela_type,
                          isFinish=False, isReplaceable=isReplaceable,
                          unFinishVersionList=constr_str_list, replaceStr=replaceStr)
        return

    # 没给版本号,默认所有的软件包都可以？？？
    if constr_str_list.__contains__(()):
        create_rela_by_name(tx, ori_pkg_id, depend_pkg_name, rela_type,
                          isFinish=True, isReplaceable=isReplaceable,
                          unFinishVersionList=[], replaceStr=replaceStr)
    else:  # 版本分析，找到合适的包，如果没有相应软件包，也按照unfinish处理
        hasMatch = False  # 可能存在找到的所有软件包都不满足条件，这时需采用处理unfinish包的方法
        for i in range(len(results)):

            depend_pkg_version = results[i][0]._properties['Version']
            if depend_pkg_version == '':
                continue
            depend_pkg_id = depend_pkg_name+depend_pkg_version

            if verSatisfyAll(depend_pkg_version, constr_str_list):
                hasMatch = True
                create_rela_by_id(tx, ori_pkg_id, depend_pkg_id, rela_type,
                            isFinish=True, isReplaceable=isReplaceable,
                            unFinishVersionList=[], replaceStr=replaceStr)
        if hasMatch is False:
            create_unfinish_pkg(tx, depend_pkg_name)
            depend_pkg_id = depend_pkg_name
            # 添加指向关系，在关系属性中注明此依赖仍未完成
            create_rela_by_id(tx, ori_pkg_id, depend_pkg_id, rela_type,
                              isFinish=False, isReplaceable=isReplaceable,
                              unFinishVersionList=constr_str_list, replaceStr=replaceStr)


def analyse_dependencies(tx, package_dict):
    for rela in dependencies:
        rela_str = get_value(package_dict, rela)
        if rela_str == "":  # 先排除掉不存在的依赖关系
            continue
        # 分析依赖，形成字典形式
        # 先根据','分解
        depend_item_list = rela_str.split(',')  # 这里每项中可能会有'[] | []'这种并列形式
        for depend_item in depend_item_list:  # 本体软件包必须依赖每个item，但这层关系该怎么表示呢？目前想到的的方法是在关系上做文章
            depend_pkg_list = depend_item.strip().split(' | ')
            isReplaceable = True
            replaceStr = rela_str
            if len(depend_pkg_list) == 1:
                isReplaceable = False
                replaceStr = ""
            # 一个依赖条目中可能会有python(<<1.5), python(>>1.1)这样的情况，即一个包可能有多个限制
            depend_dict = {}
            for depend_pkg_str in depend_pkg_list:
                # 分析依赖字符串，获取被依赖包名、版本号限制op、版本号限制ver
                depend_pkg_name = ""
                constr_op = ""
                constr_ver = ""
                pattern = r'(.*?)\((.*?) (.*?)\)'
                constr_opver = ()  # 记录符号和字符串('<<', '1.5.0')
                try:
                    constr_groups = re.search(pattern, depend_pkg_str).groups()
                    depend_pkg_name = constr_groups[0].strip()
                    constr_op = constr_groups[1].strip()
                    constr_ver = constr_groups[2].strip()
                    constr_opver = (constr_op, constr_ver)
                except AttributeError:  # 没有groups，说明没有后面的括号
                    depend_pkg_name = depend_pkg_str.strip()
                if depend_pkg_name in depend_dict:
                    depend_dict[depend_pkg_name].append(constr_opver)
                else:
                    depend_dict[depend_pkg_name] = [constr_opver]
            for depend_pkg_name in depend_dict:
                add_rela(tx, rela, package_dict, depend_pkg_name, depend_dict[depend_pkg_name], isReplaceable, replaceStr)


if __name__ == '__main__':

    parser = argparse.ArgumentParser()  # 创建一个解析对象
    parser.add_argument('--json_path', default='./xenial')
    parser.add_argument('--neo4j_uri', default=sdp_uri)
    parser.add_argument('--db_username', default=sdp_username)
    parser.add_argument('--db_password', default=sdp_password)
    json_names = []
    args = parser.parse_args()
    for _, _, fs in os.walk(args.json_path):
        json_names = fs
    driver = GraphDatabase.driver(args.neo4j_uri, auth=(args.db_username, args.db_password),
                                  max_connection_lifetime=3600 * 24 * 30, keep_alive=True)
    # contraint_nums = 0
    with driver.session() as session:
        for json_name in json_names:
            if not json_name.__contains__(".json"):  # 排除掉中间文件和gz文件
                continue
            with open(args.json_path + "/" + json_name, 'r', encoding='utf8')as fp:
                packages_store_dict = json.load(fp)
                for key, package_dict in packages_store_dict.items():
                    # contraint_nums = detect_constraint_nums(package_dict, contraint_nums)
                    # get_vers_constraint_ops(package_dict)
                    session.write_transaction(analyse_dependencies, package_dict)  # 分析每个包的依赖关系
            print("finished " + json_name)

        # with open("./all_ops.json", "w", encoding='utf-8') as f1:  ## 设置'utf-8'编码
            # f1.write(json.dumps(global_all_ops, ensure_ascii=False, indent=2, separators=(',', ': ')))  # (先转换成字符串再存储)

    driver.close()